package com.redis.lock;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.List;
import java.util.UUID;

public class ReentryRedisLock extends AbstractRedisLock {

    private static ThreadLocal<Integer> lockTimes = ThreadLocal.withInitial(() -> 0);
    private static ThreadLocal<String> threadLocalUuid = new ThreadLocal<>();

    public ReentryRedisLock(Jedis jedis, String lockName) {
        this(jedis, lockName, DEFAULT_TIMEOUT);
    }

    public ReentryRedisLock(String url, int port, String lockName) {
        this(url, port, lockName, DEFAULT_TIMEOUT);
    }

    public ReentryRedisLock(String url, int port, String lockName, long timeout) {
        this(new Jedis(url, port), lockName, timeout);
    }

    public ReentryRedisLock(Jedis jedis, String lockName, long timeout) {
        super(jedis, lockName, timeout);
    }

    @Override
    public boolean tryLock(long waitTimeOut) {
        if (lockTimes.get() > 0) {
            lockTimes.set(lockTimes.get() + 1);
            return true;
        }

        String uuid = UUID.randomUUID().toString();
        SetParams params = new SetParams();
        params.nx().ex(timeout);
        long startTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - startTime < waitTimeOut) {
            String result = this.jedis.set(lockName,  uuid, params);
            if (!"OK".equals(result)) {
                try {
                    Thread.sleep(RETRY_INTERVAL);
                } catch (InterruptedException ignored) {
                }
            } else {
                threadLocalUuid.set(uuid);
                lockTimes.set(lockTimes.get() + 1);
                return true;
            }
        }
        return false;
    }

    @Override
    public void unlock() throws RedisLockException {
        String uuid = threadLocalUuid.get();
        threadLocalUuid.remove();
        if (uuid == null) {
            throw new RedisLockException("Lock not held by this thread!");
        }
        lockTimes.set(lockTimes.get() - 1);

        if (lockTimes.get() <= 0) {
            List<String> args = Collections.singletonList(uuid);
            String result = jedis.eval(unlockLua, unlockLuaKey, args).toString();
            if (!"1".equals(result)) {
                throw new RedisLockException("The lock has been released!");
            }
        }
    }

}
